/******************************************************************************* * Copyright (c) 2007, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654 *******************************************************************************/ package org.eclipse.ui.internal.services; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.expressions.Expression; import org.eclipse.core.expressions.ExpressionInfo; import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.e4.core.commands.ExpressionContext; import org.eclipse.e4.core.contexts.ContextFunction; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.contexts.RunAndTrack; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.e4.ui.services.IServiceConstants; import org.eclipse.e4.ui.workbench.UIEvents; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.ISourceProvider; import org.eclipse.ui.ISourceProviderListener; import org.eclipse.ui.ISources; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.services.IEvaluationReference; import org.eclipse.ui.services.IEvaluationService; /** * @since 3.3 * */ public final class EvaluationService implements IEvaluationService { public static final String DEFAULT_VAR = "org.eclipse.ui.internal.services.EvaluationService.default_var"; //$NON-NLS-1$ private static final String RE_EVAL = "org.eclipse.ui.internal.services.EvaluationService.evaluate"; //$NON-NLS-1$ private boolean evaluate = false; private ExpressionContext legacyContext; private IEclipseContext context; private IEclipseContext ratContext; private int notifying = 0; private ListenerList<IPropertyChangeListener> serviceListeners = new ListenerList<>(ListenerList.IDENTITY); ArrayList<ISourceProvider> sourceProviders = new ArrayList<>(); LinkedList<EvaluationReference> refs = new LinkedList<>(); private ISourceProviderListener contextUpdater; private HashSet<String> ratVariables = new HashSet<>(); private RunAndTrack ratUpdater = new RunAndTrack() { @Override public boolean changed(IEclipseContext context) { context.get(RE_EVAL); String[] vars = ratVariables.toArray(new String[ratVariables.size()]); for (String var : vars) { Object value = context.getActive(var); if (value == null) { ratContext.remove(var); } else { ratContext.set(var, value); } } // This ties tool item enablement to variable changes that can // effect the enablement. getEventBroker().send(UIEvents.REQUEST_ENABLEMENT_UPDATE_TOPIC, UIEvents.ALL_ELEMENT_ID); return true; } }; private HashSet<String> variableFilter = new HashSet<>(); private IEventBroker eventBroker; public EvaluationService(IEclipseContext c) { context = c; ratContext = context.getParent().createChild(getClass().getName()); legacyContext = new ExpressionContext(context); ExpressionContext.defaultVariableConverter = new ContextFunction() { @Override public Object compute(IEclipseContext context, String contextKey) { Object defaultVariable = context.getLocal(DEFAULT_VAR); if (defaultVariable != null && defaultVariable != IEvaluationContext.UNDEFINED_VARIABLE) { return defaultVariable; } defaultVariable = context.getActive(IServiceConstants.ACTIVE_SELECTION); if (defaultVariable instanceof IStructuredSelection) { final IStructuredSelection selection = (IStructuredSelection) defaultVariable; return selection.toList(); } else if ((defaultVariable instanceof ISelection) && (!((ISelection) defaultVariable).isEmpty())) { return Collections.singleton(defaultVariable); } return null; } }; contextUpdater = new ISourceProviderListener() { @Override public void sourceChanged(int sourcePriority, String sourceName, Object sourceValue) { changeVariable(sourceName, sourceValue); } @Override public void sourceChanged(int sourcePriority, Map sourceValuesByName) { Iterator i = sourceValuesByName.entrySet().iterator(); while (i.hasNext()) { final Map.Entry entry = (Entry) i.next(); changeVariable((String) entry.getKey(), entry.getValue()); } } }; variableFilter.addAll(Arrays.asList(new String[] { ISources.ACTIVE_WORKBENCH_WINDOW_NAME, ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME, ISources.ACTIVE_EDITOR_ID_NAME, ISources.ACTIVE_EDITOR_INPUT_NAME, ISources.SHOW_IN_INPUT, ISources.SHOW_IN_SELECTION, ISources.ACTIVE_PART_NAME, ISources.ACTIVE_PART_ID_NAME, ISources.ACTIVE_SITE_NAME, ISources.ACTIVE_CONTEXT_NAME, ISources.ACTIVE_CURRENT_SELECTION_NAME })); context.runAndTrack(ratUpdater); } private void contextEvaluate() { evaluate = !evaluate; context.set(RE_EVAL, Boolean.valueOf(evaluate)); } protected final void changeVariable(final String name, final Object value) { if (name == null || variableFilter.contains(name)) { return; } if (value == null) { legacyContext.removeVariable(name); } else { legacyContext.addVariable(name, value); } } @Override public void addSourceProvider(ISourceProvider provider) { sourceProviders.add(provider); provider.addSourceProviderListener(contextUpdater); final Map currentState = provider.getCurrentState(); final Iterator variableItr = currentState.entrySet().iterator(); while (variableItr.hasNext()) { final Map.Entry entry = (Map.Entry) variableItr.next(); final String variableName = (String) entry.getKey(); final Object variableValue = entry.getValue(); /* * Bug 84056. If we update the active workbench window, then we risk * falling back to that shell when the active shell has registered * as "none". */ if ((variableName != null) && (!ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME.equals(variableName))) { changeVariable(variableName, variableValue); } } contextEvaluate(); } @Override public void removeSourceProvider(ISourceProvider provider) { provider.removeSourceProviderListener(contextUpdater); sourceProviders.remove(provider); final Map currentState = provider.getCurrentState(); final Iterator variableItr = currentState.entrySet().iterator(); while (variableItr.hasNext()) { final Map.Entry entry = (Map.Entry) variableItr.next(); final String variableName = (String) entry.getKey(); changeVariable(variableName, null); } contextEvaluate(); } @Override public void dispose() { for (EvaluationReference ref : refs) { invalidate(ref, false); } refs.clear(); serviceListeners.clear(); } @Override public void addServiceListener(IPropertyChangeListener listener) { serviceListeners.add(listener); } @Override public void removeServiceListener(IPropertyChangeListener listener) { serviceListeners.remove(listener); } @Override public IEvaluationReference addEvaluationListener(Expression expression, IPropertyChangeListener listener, String property) { EvaluationReference ref = new EvaluationReference(ratContext, expression, listener, property); addEvaluationReference(ref); return ref; } @Override public void addEvaluationReference(IEvaluationReference ref) { EvaluationReference eref = (EvaluationReference) ref; refs.add(eref); boolean changed = false; if (eref.getExpression() != null) { ExpressionInfo info = new ExpressionInfo(); eref.getExpression().collectExpressionInfo(info); for (String varName : info.getAccessedVariableNames()) { if (ratVariables.add(varName)) { changed = true; } } if (info.hasDefaultVariableAccess() && ratVariables.add(IServiceConstants.ACTIVE_SELECTION)) { changed = true; } } if (changed) { contextEvaluate(); } eref.participating = true; ratContext.runAndTrack(eref); } private void invalidate(IEvaluationReference ref, boolean remove) { if (remove) { refs.remove(ref); } EvaluationReference eref = (EvaluationReference) ref; eref.participating = false; eref.evaluate(); eref.hasRun = false; contextEvaluate(); } @Override public void removeEvaluationListener(IEvaluationReference ref) { invalidate(ref, true); } @Override public IEvaluationContext getCurrentState() { return legacyContext; } @Override public void requestEvaluation(String propertyName) { // Trigger evaluation of properties via context String pokeVar = propertyName + ".evaluationServiceLink"; //$NON-NLS-1$ context.remove(pokeVar); context.set(pokeVar, "link"); //$NON-NLS-1$ String[] sourceNames = new String[] { propertyName }; startSourceChange(sourceNames); for (EvaluationReference ref : refs) { Expression expr = ref.getExpression(); if (expr != null) { boolean evaluated = false; ExpressionInfo info = expr.computeExpressionInfo(); String[] names = info.getAccessedPropertyNames(); for (String name : names) { if (propertyName.equals(name)) { evaluated = true; ref.evaluate(); break; } } if (!evaluated) { names = info.getAccessedVariableNames(); for (String name : names) { if (propertyName.equals(name)) { evaluated = true; ref.evaluate(); break; } } } } } endSourceChange(sourceNames); eventBroker.send(UIEvents.REQUEST_ENABLEMENT_UPDATE_TOPIC, UIEvents.ALL_ELEMENT_ID); } /** * @param sourceNames */ private void startSourceChange(final String[] sourceNames) { notifying++; if (notifying == 1) { fireServiceChange(IEvaluationService.PROP_NOTIFYING, Boolean.FALSE, Boolean.TRUE); } } /** * @param sourceNames */ private void endSourceChange(final String[] sourceNames) { if (notifying == 1) { fireServiceChange(IEvaluationService.PROP_NOTIFYING, Boolean.TRUE, Boolean.FALSE); } notifying--; } private void fireServiceChange(final String property, final Object oldValue, final Object newValue) { for (final IPropertyChangeListener listener : serviceListeners) { SafeRunner.run(new ISafeRunnable() { @Override public void handleException(Throwable exception) { WorkbenchPlugin.log(exception); } @Override public void run() throws Exception { listener.propertyChange(new PropertyChangeEvent(EvaluationService.this, property, oldValue, newValue)); } }); } } IEventBroker getEventBroker() { if (eventBroker == null) { eventBroker = context.get(IEventBroker.class); } return eventBroker; } }